home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Grab Bag
/
Shareware Grab Bag.iso
/
003
/
db3n8509.arc
/
DB3N8509.TXT
Wrap
Text File
|
1985-09-30
|
47KB
|
1,223 lines
1 dBASE III Anomalies and Workarounds
1.1 How to use this Section
1.2 @..SAY...GET to @...SAY...PICTURE
>>> & Function
The macro (&) function will not expand properly if it is followed by a
space and parentheses. For example:
STORE 'LIST FOR' TO x
STORE 10 TO memvar
&x Field1 = memvar
will execute properly, but,
&x (Field1 - memvar) * memvar = 0
will return the "*** Unrecognized command verb" error message. This problem
can be avoided by terminating the macro-substituted memory variable with a
period. For example,
&x. (Field1 - memvar) * memvar = 0
will work. It is always a good idea to terminate a macro with a period.
1.3 APPEND to CONFIG.DB
>>> CONFIG.DB with TEDIT or WP
Config.db will not accept more than eight characters for WP or TEDIT. Any
more than eight will be truncated. Attempting to use MODIFY COMMAND (or to
edit a MEMO field) will briefly display the operating system message "Bad
command or file name" and drop the user to the dBASE dot prompt (or to the
edit screen). dBASE III warns the user that the filename is truncated when
dBASE is initialized. For example:
TEDIT=B:DFORMAT
^--- truncated
To work around this problem, place the word processor in the same drive and
directory or rename the wordprocessor with fewer characters.
1.4 COPY [STRUCTURE] to DO WHILE
>>> DO WHILE with RESTORE
If a memory variable tested in a DO WHILE loop is recreated in the loop by
RESTOREing the variable FROM a memory file, the loop will continue to run
even after the condition no longer evaluates as true (.T.). The program
below will run endlessly as long as the control variable is not the first
entry in the memory file:
var = .T.
DO WHILE var
RESTORE FROM Memfile <--- This overwrites var at the
same memory location.
var = .F. <--- This change is ignored if
ENDDO the previous assignment
statement changed the
memory location of the
variable.
RESTOREing ADDITIVE ameliorates the problem.
>>> DO WHILE with semicolon
When a DO WHILE conditional statement is continued to a second line with a
semicolon, dBASE III tries to execute this second line the second time
through the loop. When the ENDDO is encountered and the condition
evaluates as true, program flow proceeds to this second line, resulting in
the error message, "*** Unrecognized command verb." For example:
* ---This program will give an error message
* ---when "Y" is entered at the WAIT prompt.
answer = 'Y'
number = 1
DO WHILE number < 10;
.AND. answer = 'Y'
? number
WAIT '"Y" to continue ' TO answer
number = number + 1
ENDDO
If "Y" is entered at the WAIT prompt, dBASE III tries to execute ".AND.
answer = 'Y'." However, if the semicolon is deleted and the line is allowed
to wrap at column 67 in MODIFY COMMAND, execution flows correctly.
>>> DO WHILE with RETURN
A RETURN statement inside a DO WHILE...ENDDO construction will not close
the DO WHILE <condition> in the program as it does in dBASE II. Therefore,
the <condition> will continue to be evaluated. For example:
* A.PRG
expA = "1"
DO WHILE expA = "1"
? "Hi!"
DO B ---------------> * B.PRG
ENDDO expB = "2"
* EOF: A.PRG DO WHILE expB = "2"
? "How are you?"
expA = "X"
RETURN
ENDDO
* EOF: B.PRG
Executing A.PRG will cause an infinite loop. It appears dBASE III
continues to test the condition of expB. In order to work around this,
B.PRG should be written as follows:
* B.PRG (revised).
expB = "2"
DO WHILE expB = "2"
? "How are you?"
expA = "X"
EXIT <----- Notice, the EXIT here,
ENDDO
RETURN <----- and the RETURN here.
* EOF: B.PRG
>>> DO WHILE with an extra ENDDO
If an extra ENDDO is added to a command file, an infinite loop will result.
For example, the following program will execute until Esc is pressed:
number = 0
DO WHILE number < 10
@ 10,10 SAY "Now at loop " + STR(number,2)
STORE number + 1 TO number
ENDDO
ENDDO <--- This ENDDO has no matching DO WHILE.
RETURN
We recommend you use some method of indentation for the control structures:
DO CASE...ENDCASE, DO WHILE...ENDDO, and IF...ENDIF to avoid this problem.
This practice will make command files more readable, and will allow for
quick visual checking for accuracy of nested control structures. In our
example, two consecutive ENDDO statements with the same left margin is a
definite indication that something is wrong.
1.5 EJECT to LABEL limitations
1.6 MODIFY STRUCTURE to PCOL() and SET MARGIN TO
>>> Memo fields, listing or printing after a previous field
A problem displaying memo fields has been found in dBASE III by one of our
users. The problem occurs when you try to list or print a memo field
preceded by a field whose length will force the first line of the memo
field to wrap around one or more times.
Assume a file structure consisting of two fields:
Structure for database: Example.DBF
Field Field Name Type Width Dec
1 Field1 Character 100
2 Field2 Character 70
3 Comments Memo 10
** Total ** 181
These display commands
? SPACE( 1 ), Comments
? Field1, Comments
? Field2, Comments
? Field1, Field2, Comments
will all produce different results.
If the total length of the fields preceding the memo field is 29 or above,
the first line of the memo field will wrap around. The entire memo field
will then be displayed in double space. Futhermore, if the total length of
the fields preceding the memo field is long enough to force the first line
of the memo field to be displayed starting on the second line of the
display, then the entire memo field will be displayed in triple space.
1.7 RECNO() to ROUND()
>>> REPORT FORM with right margin=report width
CREATEing a REPORT FORM with a right margin value equal to the report width
(the page width minus the left margin) will display garbage to the screen
or printer. This happens beacuse there is no space to print the report.
There is a general misconception about the meaning of the right margin in
the REPORT FORM. Some users have the impression that its value is the
number of characters from the left margin, much the same way a typewriter
works. The value actually refers the the number of characters the right
margin is offset from page width.
For example,
page width |------------------------------------->|
right margin |<----------|
>>> REPORT FORM with stacked columns
CREATEing a REPORT FORM field that consists of stacked columns built with
the result of the STR() function and numeric fields may cause numbers to
display incorrectly. Specifically, entering:
STR( Field1, 5 ) + STR( Field2, 5 )
in the field contents and specifying a column width of five will result in
a correct display only when the fields have five-digit numbers stored in
them. If the number has less than five digits, the display will be
misplaced by the number of digits missing. The example above will produce:
11111
11111
if the fields are full, but:
1111
1111
1111
if there are only four digits in the fields.
1.8 SET <full-screen> to SORT
>>>SET FILTER TO with GO BOTTOM
If a SET FILTER TO condition is not satisfied by any records in the
database file and a GO BOTTOM is issued, both the EOF() and BOF() will
return a true (.T.). Removing the filter by issuing a SET FILTER TO does
not reset EOF() or BOF(). The record pointer must be repositioned to reset
EOF() and BOF(). SKIP or SKIP -1, however, will return a file boundry
error message, because EOF() and BOF() are true. To move the record
pointer appropriately issue a GO TOP and the BOF() and EOF() values will be
reset to false (.F.).
1.9 STORE to ZAP
2 dBASE III Programming
3 dBASE III Frequently Asked Questions
3.1 Installation
3.2 Commands
3.3 New data types
3.4 Memory variables
3.5 Printing
3.6 Data transfer
4 dBASE III Reference
4.1 @...GET to Boolean Operators
4.2 CHR() to FILE() function
4.3 FIND to MODIFY STRUCTURE
>>> Get Current Directory
dBASE III has no facility to get the name of the current directory. To get
it you must RUN the PC/MS-DOS command CD and import the results into dBASE
III. The basic algorithm is as follows:
1. Create or have available a general-purpose database file called
Util.DBF. Util.DBF has one field called Util_line which is
character type and has a length of 80.
2. RUN the PC/MS-DOS command CD, piping the result into a text file
entitled Util.TXT.
3. APPEND the text file Util.TXT into the database file, Util.DBF.
4. Assign to a memory variable the name of the current DOS directory
from Util.DBF.
The code that will execute this algorithm is as follows:
SET SAFETY OFF
RUN CD > Util.TXT
USE Util
ZAP
APPEND FROM Util SDF
currdir = TRIM( Util_line )
SET SAFETY ON
RETURN
>>> Get Diskspace
In the Developer's Release use the DISKSPACE() function to get the amount
of space left on the currently logged drive. The DISKSPACE() will return
the number of free bytes on the default drive as a numeric value.
dBASE III versions 1.0 and 1.1 do not have a function to return the amount
of space left on the default drive. So, to get the amount of diskspace in
these versions, use the PC/MS-DOS utility CHKDSK and import the results
into dBASE III. The basic algorithm is as follows:
1. Create or have available a general purpose database file called
Util.DBF. Util.DBF has one field called Util_line which is
character type and has a length of 80. This database file will be
useful for any of these kinds of survey operations into PC/MS-DOS.
2. RUN CHKDSK including the designator of the drive for which you
want the space statistic for, and pipe the result into a text file
entitled Util.TXT. Piping is a PC/MS-DOS capability that allows
the results of a program to be sent into a text file. It is very
useful for passing parameters between programs when there is no
formalized interface. The syntax is:
<DOS commmand> > <result text file>
^_____ DOS piping symbol
For more information on this capability, consult your PC/MS-DOS
reference manual.
3. APPEND the text file, Util.TXT, into the database file, Util.DBF.
4. Assign to a memory variable the number of free bytes on the
specified drive from Util.DBF. This operation requires that you
GOTO the record that contains the free disk space information and
then extract the number of bytes from the field using the SUBSTR()
function.
The following is a LIST of Util.DBF with the results of a CHKDSK
report. When APPENDed into a database file, the first and last
records are always blank. Records 2 through 6 contain statistics
about the currently logged disk drive. Note that this is the
currently logged DOS drive and not the DEFAULT drive SET in dBASE
III. Records 8 and 9 contain statistics about the memory
configuration of your computer. The number of bytes for each
attribute of the drive and memory occupy positions 1 through 9 in
the database field, Util_line.
Record#
1
2 9965568 bytes total disk space
3 155648 bytes in 4 hidden files
4 90112 bytes in 22 directories
5 6000640 bytes in 397 user files
6 3719168 bytes available on disk
7
8 524288 bytes total memory
9 122480 bytes free
10
The code that will get the the number of free bytes on the specified disk
drive is as follows:
SET SAFETY OFF
RUN CHKDSK > Util.TXT
USE Util
ZAP
APPEND FROM Util SDF
GO 6
diskspace = STR( SUBSTR( Util_line, 1, 9 ), 9 )
USE
SET SAFETY ON
RETURN
>>> Get Last Update and Time
To get the date of last update for the currently SELECTed database file in
the Developer's Release of dBASE III, use the LUPDATE() function.
LUPDATE() returns the date of last update as a value of date type.
dBASE III versions 1.0 and 1.1 currently do not have a function that
returns the date of last update for the SELECTed database file. To get the
date of last update in these versions of dBASE III, use the PC/MS-DOS
command DIR, and import the results into dBASE III. The basic algorithm is
as follows:
1. Create or have available a general purpose database file called
Util.DBF. Util.DBF has one field called Util_line which is
character type and has a length of 80.
2. RUN the PC/MS-DOS command DIR with the name of your database file,
piping the result into a text file entitled Util.TXT.
3. APPEND the text file Util.TXT into the database file Util.DBF.
4. Assign to a memory variable the date of last update from Util.DBF
for your database file.
The code that will get the last update of the currently SELECTed database
file is as follows:
SET SAFETY OFF
RUN DIR <Yourfile>.DBF > Util.TXT
USE Util
ZAP
APPEND FROM Util SDF
lupdate = SUBSTR( Util_line, 25, 8 )
luptime = SUBSTR( Util_line, 34, 6 )
USE
SET SAFETY ON
RETURN
>>>Installation
If the message "Insert System Disk #2 or press Ctrl-Break" appears when
dBASE III is being loaded from a hard disk and is installed, the overlay
file (DBASE.OVL) is corrupted. One possibility is that the copy on the
hard disk is corrupted and can no longer be used. Another, and more
likely, possibility is that the copy on System Disk #2 is bad, and you are
now trying to run dBASE III after just having installed to the hard disk.
If this occurs, contact the Ashton-Tate Customer Service Department.
>>> MEMO fields
(1) MEMO fields are used to contain up to 5,000 characters of text
information that is to be associated with a database record. Information
may be read into a MEMO field using Ctrl-K-R and written to text files
using Ctrl-K-W. Information from MEMO fields can be displayed or printed
by using LIST, DISPLAY, ?. The field must be specified with these
commands. However, these commands cause the MEMO field to wrap at 50
columns. The REPORT FORM may be used to output MEMO fields with line
widths of more or less than 50 characters.
(2) PACKing a database file with memo fields will not decrease the amount
of disk space used by the .DBT file. The command file below demonstrates
how to remove the deleted records and free the unused disk space.
SET DELETED ON
USE Filea
COPY TO Temp
CLOSE DATABASE
ERASE Filea.dbf
ERASE Filea.dbt
RENAME Temp.dbf TO Filea.dbf
RENAME Temp.dbt TO Filea.dbt
SET DELETED OFF
4.4 Numeric fields to PARAMETERS
4.5 PRIVATE to PROCEDURE
4.6 PUBLIC to REPORT FORM
>>> Ramdisk
There are several options for users who wish to use a ramdisk in
combination with dBASE III.
1. For faster operation of applications that utilize routines stored
in the DBASE.OVL file, you may wish to put the .OVL in a ramdisk.
(a) The minimum drive size will have to be in excess of 181,000
bytes. The DBASE.OVL file for version 1.1 is 180,736 bytes.
The total amount of RAM in your machine must be more than
440,000 bytes in order to do this. Additionally, if you are
using a CONFIG.DB, it must be present on the drive where .OVL
resides.
(b) Boot dBASE III from the ramdisk by calling for the DBASE.COM
from the drive on which it resides. For example: drive D: is
the ramdisk and the DBASE.COM is on the C: drive.
C> D:
D> C:DBASE
2. It may be a very useful area for procedure or command files to be
run from, increasing the speed of processing. Prior to entering
dBASE III, copy the appropriate files to the ramdisk. Once in
dBASE III, SET the DEFAULT TO the ramdisk drive and proceed.
3. It is also useful as a small work area to manipulate utility and
temporary files. The useage tips on getting the current directory
or diskspace are good examples of where a small ramdisk would be
extremely useful and time efficient.
>>> REPORT FORM
The semicolon is not documented as functioning as a Carriage
Return/Line-Feed in certain parts of REPORT FORMs.
>>> REPORT FORM grouped by week
If you have a date-oriented report and you need to have it grouped by week,
the following discussion will assist you.
The grouping of dates into weeks has two requirements. First, the database
file you are reporting from must be INDEXed on the date field that is being
grouped on. Second, as the group expression in your REPORT FORM, you must
have an expression that returns as its value the first day of the week for
each date field. The expression is as follows:
Yourdate - ( DOW( Yourdate ) - 1 )
When given any date value, this expression returns the date of the previous
Sunday. It does this by subtracting from your date field the number of
days that have passed since the last Sunday, the first day of the week in
the dBASE III calendar. This value is obtained by subtracting 1 from the
result of the DOW() function.
If you wish to have the week you are grouping on start on a later day such
as Monday, subtract more from the result of the DOW() function. For
example, Monday would be DOW() - 2, Tuesday DOW() - 3, and so on.
4.7 RELEASE to SET PROCEDURE
4.8 SET RELATION to warnings
>>>WAIT TO
When a function key is SET to a literal character string, the WAIT command
will not accept the assigned string, although ACCEPT TO will. Instead, the
WAIT command will take the ASCII code respresentation of the function key
itself. For example:
SET FUNCTION 10 TO "A"
WAIT TO var
* ---Press F10.
DISPLAY MEMORY
VAR pub C "v"
1 variables defined, 3 bytes used
255 variables available, 5997 bytes available
* ---Test to see what ASCII character F10 sent.
? ASC(var)
246 <------------------------- Code for F10
? ASC("A")
65 <-------------------------- Code for "A"
4.9 dBase III File Structure
4.10 dBASE III Memo File Structure
4.11 Installation and Configuration
5 dBASE III Sample Programs
6 dBASE III Technical Notes
6.1 Shifted Data Displays
by OLiver Biggerstaff
A database file may become corrupted for any number of reasons. Often the
corruption may be the form of shifted data in full-screen edit screen
displays. This is caused by an embedded null character in a record. A
null character is represented as a 00 hex and is used by dBASE II and dBASE
III as a string terminator. A string terminator is a character that can be
thought of as a delimiter, much like double quotes surrounding a character
string, or as a carriage return and linefeed at the end of a record.
The dBASE APPEND, EDIT, BROWSE, and other full-screen commands work on the
principle that the cursor's positioning on the screen depends on certain
attributes, such as the length of a field and its current position. The
appearence of shifted data is caused by the embedded string terminator
forcing dBASE to stop the display of a field prematurely, placing the
cursor at an incorrect location. If a null character is encountered before
all the characters of a field have been displayed, dBASE will stop listing
that particular field and will produce the shift effect by displaying the
next field at an incorrect screen location.
The data is actually not shifted physically in the database file. It is
simple, therefore, to correct the database by replacing any null character
with another character that does not force dBASE to display incorrectly. A
good choice for this character is the ASCII character zero (0) or 30 hex.
Replacing a null with this character is advantageous for two reasons:
1. Since dBASE can display it, you can locate the corrupted data.
2. A zero character will have the least disruptive effect on the
contents of the database file. Additionally, logical fields that
contain a zero character will be displayed as (.F.)
Once all the null characters have been replaced, the BROWSE or EDIT
commands can be used to retype the original data in place of the characters
that replaced the nulls.
The following is one of many methods that can be used to replace null
characters with other characters. In this example we use the PC/MS-DOS
utility DEBUG.COM, since most of you have this program on your supplemental
PC/MS-DOS disk. These examples assume that the database file is 64K or
less in size. Refer to the PC/MS-DOS manual for more information on DEBUG
and how to use data segments if the database file is larger than 64K.
The contents contained in the < and > symbols must be calculated by you,
and entered without the symbols. For example, if the value of the CX
register is 2C80H, then <CX+100H> is to be replaced with 2D80H. Be sure
that before you attempt this procedure, you have made a backup of your
database file.
For dBASE II users on a 16-bit computer:
C>DEBUG <database>.DBF ;Read database file into memory.
-RCX ;Get the value in the CX
;register.
-S 309 <CX+100H> 00 ;Search for nulls in the file.
.
. ;A list appears here of one or
. ;more addresses containing a
. ;null.
-E <address> 30 ;Replace each individual address
;that contains a null with a
;zero.
-W ;Save the modifications to disk.
-Q ;Quit DEBUG.
For dBASE III users:
C>DEBUG <database>.DBF ;Read database file into memory.
-RCX ;Get the value of the CX
;register.
-S 100H 1121H 0DH ;Search for the end of the
;header for address containing
;0DH
xxxx:yyyy ;for address containing 0D.
;Search for null characters.
-S <yyyy+2> <CX+100H> 00
.
. ;List of addresses containing
. ;nulls.
.
-E <address> 30H ;Replace each null with a zero.
-W ;Save the modifications to
;disk.
-Q ;Quit DEBUG.
Unfortunately, this method of replacing null characters can be very tedious
if many characters must be replaced. For those of you with a large amount
of corrupted data, it is suggested that you use other well-known utility
programs such as NIBBLER, NORTON UTILITIES, JAZ, or PATCH. These programs
will allow you to look at very large database files directly from the hard
disk. Some of these programs may also have a global search and replace
option.
6.2 Swapping Printer Ports on the IBM PC
by Robert Boies
Last month in the dBASE II section of TechNotes we presented an assembler
routine to swap printer ports on an IBM PC. This routine allows the user
or programmer to toggle the LPT1 and LPT2 ports, making it easy to redirect
output to several printers. This month we present essentially the same
routine configured for dBASE III.
If you have versions 1.0 or 1.1 of dBASE III, you will have to create an
.COM file from this routine and at runtime use the RUN command to call it.
For example from within dBASE III,
* ---Print a first report to the default port, LPT1.
REPORT FORM One TO PRINT
* ---Print a second report to LPT2.
RUN Portswap <---------------------------- Redirect to LPT2.
REPORT FORM Two TO PRINT
RUN Portswap <---------------------------- Restore LPT1.
If you have the Developer's Release of dBASE III, you will be able to use
the assembly language interface implemented in that version. For example:
* ---LOAD into memory.
LOAD Portswap
* ---Print a first report to to the default port, LPT1.
REPORT FORM One TO PRINT
* ---Print a second report to LPT2.
CALL Portswap <---------------------------- Redirect to LPT2.
REPORT FORM Two TO PRINT
CALL Portswap <---------------------------- Restore LPT1.
* ---RELEASE the memory space.
RELEASE MODULE Portswap
The listing below, Portswap.ASM, uses the conditional directives discussed
earlier to allow you to specify whether you wish to create a .BIN or .COM
file.
Assembly language source code:
; Program ..: Portswap.ASM
; Author ...: Robert Boies
; Date .....: September 1, 1985
; Note .....: Swaps the LPT1 and LPT2 ports in PC/MS-DOS 2.x
.LFCOND ; List false conditionals.
PAGE 60,132 ; Page length 60, line 132.
COM EQU 0 ; Assemble as .BIN file
D3 EQU 1 ; for Developer's Release.
;
CODESEG SEGMENT BYTE PUBLIC 'CODE'
ASSUME CS:CODESEG
;
PORTSWAP PROC FAR
;
IF COM
ORG 0100H ; Originate at 0100H.
ENDIF
;
START: PUSH ES ; Save environment.
PUSH AX
PUSH BX
PUSH DI
;
MOV AX,40H ; System stores critical operating
; parameters at segment 40H (absolute
; address 400H).
; Load this segment address into AX.
PUSH AX ; Put it on the stack.
POP ES ; Pop the segment id from the
; stack into extra segment register.
MOV DI,8H ; Port address of LPT1 and LPT2 are
; stored at offsets 8H and 0AH in
; this segment. Load DI with offset.
MOV AX,ES:[DI] ; Put the port address of the
; first printer into the accumulator.
INC DI ; Increment the destination index
; twice to point to port address
INC DI ; of the second printer.
MOV BX,ES:[DI] ; Put the port address of the
; second printer into BX.
MOV ES:[DI],AX ; Poke the address of the first
; printer into the location of the
; second printer.
DEC DI ; Decrement DI twice to point to the
DEC DI ; location of the first printer
; port address.
MOV ES:[DI],BX ; Poke the address of the second
; printer into the location of the
; first.
POP DI ; Restore environment.
POP BX
POP AX
POP ES
IF COM
INT 20H ; INT 20H if .COM file
ELSE
RET ; far return to dBASE III
ENDIF
;
PORTSWAP ENDP
CODESEG ENDS
END START
6.3 Interfacing Assembly Language Routines with dBASE
by Ralph Davis
Creating Assembler Programs with DEBUG
DEBUG is the assembly language programmer's best friend. It is a powerful tool for
exploring the computer's memory, testing assembly language programs, studying program
listings, and creating new programs. Additionally, it can be used to rebuild corrupted
data files, convert hidden files to accessible files, or simply analyze file structures.
Our main interest in DEBUG here is to create assembly language routines for use with dBASE
II and dBASE III.
It is tempting to use DEBUG because of its interpreter-like qualities. You can quickly
enter code and then see if it works.If it does, you call it <PROGRAM>.COM and write it to
disk. If it doesn't, you trace through the old code, enter new code, and try again.
Eventually, you come up with a program that works through trial-and-error. However, this
can lead to sloppy programming habits and inefficient code, so it is important to bear in
mind what you want a particular program to accomplish.
DEBUG has some limitations. Most importantly, it only recognizes absolute addresses.
When you write a program for submission to an assembler, you label the instructions and
data you will need to refer to, then refer to them with the label. You don't need to know
the actual addresses. DEBUG, on the other hand, obliges you to look through your program
listing and find addresses whenever you refer to them. For instance, instead of entering
JMP EXIT, you must enter JMP 02FC. Instead of CALL HEXPRINT, you use CALL 05AE. Instead
of MOV BX, OFFSET DATA, you need MOV BX, 0105. If your routine is small, this does not
present a problem. But as you add features and it becomes larger, this becomes a serious
impediment. If you add or alter instructions, thereby changing an absolute address, you
have to change every reference to it. And the only way to find the references is to page
through the entire program, line by line. For this reason, DEBUG is best for creating
short utility programs.
Most often, programs created with DEBUG use BIOS or DOS interrupts to manipulate the
hardware. Some typical functions that appear in this issue are setting the cursor (see
the example on page 4-72C of the Developer's Release Reference Manual and the program
listed in this issue), manipulating the shift keys, or swapping printer ports. Programs of
this type should not contain any subroutines.
DEBUG has another important limitation: it only understands hexadecimal numbers. There is
simply nothing you can do to make it accept decimal numbers. This is not a problem when
entering addresses or interrupt numbers, as most assembly language programmers think these
values in hexadecimal anyway. But very few programmers think in hex when doing
calculations. DEBUG is therefore not a good tool for doing number-crunching of even
intermediate complexity. Although there are utilities available to assist in this
process, such as Sidekick, this is still a major obstacle to doing extensive calculations
within DEBUG.
Another problem with DEBUG is that code produced with it can be extremely obscure. Trying
to decipher the flow of a program where you have only absolute addresses and hexadecimal
numbers to guide you can be very frustrating. In addition, DEBUG does not support
comments. So when you read a DEBUG listing, you are, for all intents and purposes,
reading "machine English." The machine expresses its own language in cryptic English-like
symbols, making a few grudging concessions to your desire to understand it. All of this
reinforces what we suggested earlier: keep DEBUG routines short.
The program from the Developer's Release Reference Manual mentioned above is a good
example of a program appropriate for DEBUG. The listing on page 4-72C is as follows:
_PROG SEGMENT BYTE
ASSUME CS:_PROG
;
CURSOR PROC FAR ; Force a far return.
;
MOV CX,[BX] ; Get two HEX digits.
MOV AH,1 ; Set cursor type.
INT 10H ; Video interrupt.
RET ; Do a far return.
;
CURSOR ENDP
;
_PROG ENDS
END
This is a terse routine that converts the dBASE III cursor to a full-sized box when
CHR(18) passed as a parameter to it. Notice one thing about this code: it has six lines
of assembler directives (the first three and the last three), and only four lines of
machine instructions. In a short program like this one, there is no advantage to
assembling, linking, and converting it using MASM, LINK, and EXE2BIN. DEBUG is faster and
easier.
Here is a DEBUG session that enters this program as a .COM file.
(The DEBUG commands are explained in Chapter 8 of the PC/MS-DOS manual. Page numbers
which follow refer to it.)
D>debug
First give DEBUG the 'A' (assemble) command (page 8-15) and enter the program.
-A
6257:0100 MOV CX,[BX]
6257:0102 MOV AH,1
6257:0104 INT 10
6257:0106 INT 20
6257:0108
Notice that 'INT 20' is our last instruction, not 'RET' as the manual indicates. We will
explain this shortly.
The address following the last instruction is 108. Therefore, enter eight into CX using
the 'R' (register) command [page 8-41]. This tells DEBUG the number of bytes to write to
disk.
-RCX
CX 0000
:8
Name the program CURSOR.COM using the 'N' command [page 8-37], and write it to disk using
'W' [page 8-55].
-NCURSOR.COM
-W
Writing 0008 bytes
This is the basic procedure for creating a .COM file from DEBUG. CURSOR.COM will yield
unpredictable results executed from PC/MS-DOS, since the registers are not preserved, and
we have no way of knowing what is being passed in DS:BX. (When we tested it, the cursor
simply vanished.) Nor, in its present form, will it work in dBASE III. It needs a couple
of changes to make it work, but this point deserves some attention.
PC/MS-DOS .COM files and dBASE LOAD modules require slightly different specifications. A
.COM file must be ORGed (originated) at address 100H, and it must end with a command like
INT 20H (terminate) or INT 27H (terminate and stay resident); a simple RET will not return
correctly. dBASE III, on the other hand, requires LOAD modules to be ORGed at address 0
and to return to dBASE III with a far return, RETF. If you load a conventional .COM file,
ORGed at 100H and terminated with INT 20H, into dBASE III, and then call it, you will lock
the system, even if it works from PC/MS-DOS. When DEBUG writes a program to disk, it
writes a binary file -- that is, a file which contains nothing but the machine
instructions you have given it. Therefore, we need not concern ourselves with ORGing
programs correctly at this stage. We do have to terminate LOAD modules with RETF, however.
Here is a DEBUG session that enters this program as a .BIN file which will execute from
dBASE III.
D>debug
Type 'A' for assemble. Terminate with a RETF.
-A
6346:0100 MOV CX,[BX]
6346:0102 MOV AH,1
6346:0104 INT 10
6346:0106 RETF
6346:0107
Place the number 7 in the CX register to save 7 bytes to disk.
-RCX
CX 0000
:7
Name the file, and write it.
-NCURSOR.BIN
-W
Writing 0007 bytes
Quit DEBUG.
-Q
The page of the Developer's Release Manual referred to above gives the following example
of how to use Cursor:
LOAD Cursor
STORE CHR(18) TO shape
CALL Cursor WITH shape
The commands to convert the cursor back to its normal format are:
LOAD Cursor
STORE CHR(12) + CHR(11) to shape
CALL Cursor WITH shape
.COM Files vs. .EXE Files
When creating programs with a full-featured assembler, we have two options: .COM files and
.EXE files. Each has advantages and disadvantages.
.COM files are an inheritance from the world of 8-bit CP/M. They are your only option if
you have a CP/M machine. .COM files must adhere to a strictly defined structure.
1. They must fit entirely within one segment. All segment registers must point to
the same address, and cannot be changed during the execution of the program. This
means that all of our main program, subroutines, and data must fit in 64K. A 64K
.COM file is a very large program -- each line of code assembles to between 1 and
6 bytes, so a 64K .COM file could have as many as 30,000 lines of source code.
2. They must be ORGed at 100H. When PC/MS-DOS loads a .COM file, it jumps to CS:100H
and begins executing.
3. They must return control to their calling routine with either INT 20H or INT 27H,
or the equivalent INT 21H function calls, 4CH and 31H.
.COM files load more quickly than .EXE files, since no addresses need to be calculated at
load time.
The assembly language programs that dBASE II and dBASE III can execute as subroutines
(with the CALL command) are variations of the .COM file. We will discuss the specifics of
their formats later.
.EXE files are less limited structurally. The segment registers can be freely
manipulated, and each one can point to an entirely different 64K segment. .EXE files can
therefore be much larger than .COM files. .EXE files were designed to take better
advantage of the actual architecture of 16-bit 8086-based microprocessors. Having data in
one segment, code in another, and the stack in a third allows much greater utilization of
the memory space available in today's machines. It also provides us the semblance of
structured programming in assembly language. The SEGMENT, PROC, ENDS, and ENDP operators
give a program listing a much more organized appearance than it has with JMP and DB
statements interspersed throughout the code.
.EXE files take longer to load than .COM files, as many of the absolute addresses are not
computed until load time. They also take up more disk space than .COM files. However,
since they use much more of the 8086 family's capabilities, they can be much more powerful
programs. The commercial programs which were handed down from the CP/M world are all .COM
files, whereas those which were created since the advent of 16-bit machines are mostly
.EXE files.
Having said this, we will leave .EXE files behind. You cannot LOAD .EXE files from dBASE
II or dBASE III. You can execute them with QUIT TO in dBASE II or RUN(!) in dBASE III.
If you want to pass parameters to and from .EXE files, you must pass them in text
files(the SDF format is recommended).
Adapting Assembly Language Programs to dBASE II or III
As mentioned earlier, the format of a dBASE II or III assembly language subroutine most
closely resembles that of a .COM file. Most importantly, it must reside in one segment.
Since it is intended as a subroutine, not as a stand-alone program, it will differ
somewhat from a standard .COM file.
For one thing, a .COM file must be ORGed at 100H. However, ORGing a dBASE (II or III)
subroutine at 100H will cause it to fail. A program intended for use in dBASE II must be
ORGed high in the code segment -- the exact address depends on the version of dBASE II,
the later the version, the higher the address. In version 2.43*, the ORG address should
be above 61440 decimal. (See Robert Boies' article on swapping printer ports in the August
issue of TechNotes for a good example of a dBASE II assembly language program.) A program
intended for dBASE III must be ORGed at 0 (that is, it need not have an ORG statement).
Secondly, .COM files return to their caller with interrupts (usually 20H or 27H), whereas
dBASE II and dBASE III routines require RET (return) -- near for dBASE II, far for dBASE
III.
The procedure for converting assembly language source code into programs dBASE II or III
can execute are as follows:
1. For dBASE II, you must assemble your program with an assembler that produces a
file in Intel .HEX format. Intel's assemblers, ASM (for CP/M) and ASM86 (for
CP/M-86), create such a file. For PC/MS-DOS, the Seattle Computer Products
assembler generates a .HEX file. Refer to their manuals, as their assembly
language syntax differs somewhat from Microsoft's and IBM's.
2. For dBASE III, use the IBM or Microsoft Macro-Assembler (MASM.EXE) to produce a
.OBJ (object) file. Enter the command as follows:
MASM <filename> <filename> <filename>;
The third parameter will cause MASM to produce a listing file with a .LST
extension, which is very useful for debugging.
3. Use the linker utility (LINK.EXE) that comes both with PC/MS-DOS and with the
assembler. This will create an .EXE file. The command is:
LINK <filename>
Press Return three times in response to the prompts.
4. Use EXE2BIN.EXE to convert the program to .COM or .BIN format. If you are
creating a .BIN file, you need only enter one parameter in the command line:
EXE2BIN <filename>
If you are creating a .COM file, you need to specify the full target filename:
EXE2BIN <filename> <filename>.COM
Using Conditional Assembler Directives
Because the differences between .COM files and .BIN files are minor, it is possible to
generate both using the same source code. The following program skeleton shows how to set
this up. The EQU statements at the top inform the assembler whether we are assembling a
program for PC/MS-DOS or dBASE III. In the present example, we have set COM equal to 0
(meaning false) and D3 equal to 1 (non-zero, meaning true). We then use conditional
directives to tell the assembler how we want the program created. Conditional directives
are statements in your assembly program to direct the assembler to assemble a block of
instructions based on a variable value. For example, IF COM (if COM is not zero), ORG the
program at offset 100H. Then at the end of the program, IF COM, exit with INT 20H;
otherwise, exit with a far RET.
.LFCOND ; List false conditionals,
PAGE 60,132 ; page length 60, line 132.
COM EQU 0 ; Assemble program as .BIN
D3 EQU 1 ; file for dBASE III.
CODESEG SEGMENT BYTE PUBLIC 'CODE'
ROUTINE PROC FAR
ASSUME CS:CODESEG,DS:CODESEG
IF COM
ORG 100H
ENDIF
PUSH DS ; Make sure DS points to
PUSH CS ; the current
POP DS ; segment.
.
. (program goes here)
.
.
POP DS ; Restore caller's DS.
IF COM
INT 20H ; INT 20H if .COM file.
ELSE
RET ; Far return if dBASE III
ENDIF
ROUTINE ENDP
CODESEG ENDS
END
It is very important to load the DS register with the segment address contained in CS.
PC/MS-DOS does this automatically for a .COM file, but dBASE III does not. Therefore, if
your routine needs to access its own data, it will need to set DS correctly.
Sample Program With Conditional Assembly
Here is an program built on the skeletal structure which sets condensed print on an EPSON
printer.
; Program ...: Printer.ASM
; Author ....: Ralph Davis
; Date ......: September 1, 1985
TITLEPRINTER.ASM -- sets condensed print
.LFCOND
PAGE60,132
COMEQU0
D3EQU1
CODESEGSEGMENTBYTE PUBLIC 'CODE'
PRINTER PROCFAR
ASSUME CS:CODESEG,DS:CODESEG
IFCOM
ORG100H
ENDIF
START:JMPSHORT ENTRY; Jump past data.
CODESDB27,64,27,15; Printer control codes.
CODELEN EQU$-CODES; Length of string.
ENTRY: PUSHAX; Save registers.
PUSHBX
PUSHDS
PUSHCS; Set up DS
POPDS; with current segment.
PUSHCX; Save CX
PUSHDX; and DX.
MOV BX,OFFSET CODES; Point BX to codes.
MOVCX,CODELEN; Length of string.
; Controls the loop.
GET_CODE:
MOV DL,BYTE PTR [BX] ; Get code to send.
MOV AH,5H ; PC/MS-DOS function 5H,
INT 21H ; (send char to printer).
INC BX ; Point to next code
LOOP GET_CODE ; and print it.
POPDX; Restore registers.
POPCX
POPDS
POPBX
POPAX
IFCOM
INT 20H ; INT 20H if .COM file.
ELSE
RET; Far return to dBASE III.
ENDIF
PRINTER ENDP
CODESEG ENDS
ENDSTART; End assembly.
Assemble this program according to the instructions given earlier. To run it from dBASE
II or dBASE III versions 1.0 and 1.1, assemble it as a .COM file, and enter the following
commands:
dBASE II:
QUIT TO 'Printer'
dBASE III:
RUN Printer
To run it from the Developer's Release of dBASE III, assemble it as a .BIN file, and use
these commands:
LOAD Printer
CALL Printer
7 dBASE III Version 1.1 Change Summary